#ifndef OPTFRAME_NSSEQVRP2OPT_HPP_
#define OPTFRAME_NSSEQVRP2OPT_HPP_

// Framework includes
#include "../OptFrame/Move.hpp"
#include "../OptFrame/NSSeq.hpp"

using namespace std;

// Working structure: vector<T>

class MoveVRP2Opt: public Move<RepPRV, MemPRVP>
{
protected:
	int p1, p2; // position 1 and position 2, respectively
	int r; //route

	PRVPProblemInstance& pPRVP;

public:

	MoveVRP2Opt(PRVPProblemInstance& _pPRVP, int _r, int _p1, int _p2) :
		pPRVP(_pPRVP), r(_r), p1(_p1), p2(_p2)
	{
	}

	virtual ~MoveVRP2Opt()
	{
	}

	int get_p1()
	{
		return p1;
	}

	int get_p2()
	{
		return p2;
	}

	int get_r()
	{
		return r;
	}

	bool canBeApplied(const RepPRV& rep)
	{
		bool all_positive = (p1 >= 0) && (p2 >= 0) && (r >= 0);
		return all_positive && (rep.at(r).size() >= 2);
	}

	MoveVRP2Opt& apply(RepPRV& rep)
	{
		int menor, maior;
		if (p1 <= p2)
		{
			menor = p1;
			maior = p2;
		}
		else
		{
			menor = p2;
			maior = p1;
		}

		reverse(rep.at(r).begin() + menor, rep.at(r).begin() + maior);

		return *new MoveVRP2Opt(pPRVP, r, p1, p2);
	}

	MoveVRP2Opt& apply(MemPRVP& m, RepPRV& rep)
	{

		Matrix<double>& dist = (*pPRVP.dist);
		double delta = 0;

		//para facilitar as contas, adicionando o depósito no final da rota
		rep.at(r).push_back(0);
		int menor, maior;
		if (p1 <= p2)
		{
			menor = p1;
			maior = p2;
		}
		else
		{
			menor = p2;
			maior = p1;
		}

		if (menor - 1 < 0)
			delta += -dist(0, rep.at(r).at(menor));
		else
			delta += -dist(rep.at(r).at(menor - 1), rep.at(r).at(menor));

		delta += -dist(rep.at(r).at(maior - 1), rep.at(r).at(maior));

		if (menor - 1 < 0)
			delta += dist(0, rep.at(r).at(maior - 1));
		else
			delta += dist(rep.at(r).at(menor - 1), rep.at(r).at(maior - 1));

		delta += dist(rep.at(r).at(menor), rep.at(r).at(maior));

		//removendo os depósitos que facilitaram a conta
		rep.at(r).pop_back();
		//setando as variações de fo e inviabilidade
		m.setDelta(delta);
		m.setDelta_inf(0.0);
		//executando o movimento
		reverse(rep.at(r).begin() + menor, rep.at(r).begin() + maior);
		// r1->r1, r2->r2, e1->i1, e2->i2, n1->n2, n2->n1, i1->e1, i2->e2
		return *new MoveVRP2Opt(pPRVP, r, p1, p2);
	}

	virtual bool operator==(const Move<RepPRV, MemPRVP>& _m) const
	{
		const MoveVRP2Opt& m1 = (const MoveVRP2Opt&) _m;
		return ((m1.p1 == p1) && (m1.p2 == p2) && (m1.r == r));
	}

	void print() const
	{
		cout << "MoveVRP2Opt( route: " << r << " : ";
		cout << " edge " << p1 << " <=>  edge " << p2 << " )";
		cout << endl;
	}
};

class NSIteratorVRP2Opt: public NSIterator<RepPRV, MemPRVP>
{

private:

	MoveVRP2Opt* m;
	int index;
	vector<MoveVRP2Opt*> moves;
	const RepPRV& rep;

	PRVPProblemInstance& pPRVP;

public:

	NSIteratorVRP2Opt(PRVPProblemInstance& _pPRVP, const RepPRV& _r) :
		pPRVP(_pPRVP), rep(_r)
	{
		index = 0;
		m = NULL;
	}

	virtual ~NSIteratorVRP2Opt()
	{
	}

	void first()
	{
		for (int r = 0; r < rep.size(); r++)
		{
			int tam = rep.at(r).size() - 2;
			for (int p1 = 0; p1 < tam; p1++)
			{
				for (int p2 = p1 + 2; p2 < rep.at(r).size(); p2++)
				{
					moves.push_back(new MoveVRP2Opt(pPRVP, r, p1, p2));
				}
			}
		}
		if (moves.size() > 0)
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	void next()
	{
		index++;
		if (index < moves.size())
		{
			m = moves[index];
		}
		else
			m = NULL;
	}

	bool isDone()
	{
		return m == NULL;
	}

	MoveVRP2Opt& current()
	{
		if (isDone())
		{
			cout << "There isnt any current element!" << endl;
			cout << "NSSeqVRP2Opt. Aborting." << endl;
			exit(1);
		}

		return *m;
	}
};

class NSSeqVRP2Opt: public NSSeq<RepPRV, MemPRVP>
{

private:
	PRVPProblemInstance& pPRVP;

public:

	NSSeqVRP2Opt(PRVPProblemInstance& _pPRVP) :
		pPRVP(_pPRVP)
	{
	}

	virtual ~NSSeqVRP2Opt()
	{
	}

	MoveVRP2Opt& move(const RepPRV& rep)
	{
		int r = rand() % rep.size();
		if (rep.at(r).size() < 3)
			return *new MoveVRP2Opt(pPRVP, -1, -1, -1);

		int p1 = rand() % (rep.at(r).size() + 1);

		int p2;

		do
		{
			p2 = rand() % (rep.at(r).size() + 1);
		} while (abs(p1 - p2) < 2);

		// create 2-opt(p1,p2) move
		return *new MoveVRP2Opt(pPRVP, r, p1, p2);
	}

	virtual NSIteratorVRP2Opt& getIterator(const RepPRV& r)
	{
		return *new NSIteratorVRP2Opt(pPRVP, r);
	}

	virtual void print()
	{
		cout << "NSSeqVRP2Opt" << endl;
	}
};

#endif /*OPTFRAME_NSSEQVRP2OPT_HPP_*/
